Skip to content

Validator chain invariants#1774

Open
sergerad wants to merge 1 commit intonextfrom
sergerad-validator-chain
Open

Validator chain invariants#1774
sergerad wants to merge 1 commit intonextfrom
sergerad-validator-chain

Conversation

@sergerad
Copy link
Collaborator

@sergerad sergerad commented Mar 11, 2026

Context

Closes #1773.

In #1764 we are adding a validator subcommand to construct and sign the genesis block. In this PR, we are building on top of that and storing the genesis block as the initial chain tip in the validator DB.

The validator is updated to maintain a chain tip as it continues to sign blocks. It uses this state to validate chain continuity (block number sequence, commitment references, etc).

Changes

  • Added a block_headers table to the validator database.
  • Bootstrap now initializes the validator database with the genesis block header as the chain tip (new --data-directory argument).
  • validate_block now enforces chain continuity: sequential block numbers and matching previous block commitments.
  • validate_block allows for the current chain tip in block_headers table to be overwritten to allow for idempotence.
  • Made the db module public so the bootstrap command can call save_chain_tip and load directly.

@sergerad sergerad added the no changelog This PR does not require an entry in the `CHANGELOG.md` file label Mar 11, 2026
@sergerad sergerad requested review from Mirko-von-Leipzig, bobbinth and mmagician and removed request for bobbinth March 11, 2026 00:47
@sergerad sergerad marked this pull request as ready for review March 11, 2026 00:48
Copy link
Collaborator

@Mirko-von-Leipzig Mirko-von-Leipzig left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nits, but I think we need to allow for a single block fork.

/// block commitment).
///
/// On success, returns the signature and the new block header (which becomes the new chain tip).
#[instrument(target = COMPONENT, skip_all, err, fields(chain_tip = chain_tip.block_num().as_u32()))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also log block.number and block.commitment

Copy link
Collaborator Author

@sergerad sergerad Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

those aren't available from ProposedBlock, if thats what you mean. I could add an event in the fn? After

    let (proposed_header, _) = proposed_block

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avoid events, but we can inject the value within the function using span.set_attribute

@sergerad sergerad changed the title Validator chain continuity check Validator chain invariants Mar 12, 2026
Base automatically changed from sergerad-validator-bootstrap to next March 12, 2026 06:16
@sergerad sergerad force-pushed the sergerad-validator-chain branch from 8586d1d to e2dee35 Compare March 12, 2026 06:47
Comment on lines +46 to +47
#[error("conflict: {0}")]
Conflict(String),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit weird - should we not have more specific variants?

CREATE INDEX idx_validated_transactions_block_num ON validated_transactions(block_num);

CREATE TABLE block_headers (
block_num INTEGER PRIMARY KEY,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we also store block_commitment can we avoid pulling in the entire header each check?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this breaks under concurrent requests. This is compounded by having separate database transactions/queries instead of using a single transaction throughout.

I'm unsure if using a single database tx would help either, since its possible to concurrently perform an upsert on block N, while storing N+1 based on the old value.

A simple fix is adding a Semaphor with exactly one permit after deserialization. I still don't like that we keep opening new database transactions multiple times per request though. This is also a problem in the store, but that's a diesel problem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog This PR does not require an entry in the `CHANGELOG.md` file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Validator ensures canonical chain

2 participants